package expr

import ast.expr.SqlBinaryOperator
import ast.expr.SqlExpr
import ast.expr.SqlSubQueryPredicate
import ast.order.SqlOrderByOption
import dsl.const
import query.select.SelectQuery

sealed class Query(open var alias: String?) {
    constructor() : this(null)

    operator fun plus(query: Query): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.ADD, query)
    }

    operator fun <T> plus(value: T): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.ADD, const(value))
    }

    operator fun minus(query: Query): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.SUB, query)
    }

    operator fun <T> minus(value: T): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.SUB, const(value))
    }

    operator fun times(query: Query): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.MUL, query)
    }

    operator fun <T> times(value: T): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.MUL, const(value))
    }

    operator fun div(query: Query): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.DIV, query)
    }

    operator fun <T> div(value: T): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.DIV, const(value))
    }

    operator fun rem(query: Query): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.MOD, query)
    }

    operator fun <T> rem(value: T): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.MOD, const(value))
    }

    infix fun eq(query: Query): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.EQ, query)
    }

    infix fun <T> eq(query: T): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.EQ, const(query))
    }

    infix fun eq(query: SelectQuery): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.EQ, QuerySub(query))
    }

    infix fun ne(query: Query): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.NE, query)
    }

    infix fun <T> ne(query: T): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.NE, const(query))
    }

    infix fun ne(query: SelectQuery): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.NE, QuerySub(query))
    }

    infix fun gt(query: Query): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.GT, query)
    }

    infix fun <T> gt(query: T): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.GT, const(query))
    }

    infix fun gt(query: SelectQuery): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.GT, QuerySub(query))
    }

    infix fun ge(query: Query): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.GE, query)
    }

    infix fun <T> ge(query: T): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.GE, const(query))
    }

    infix fun ge(query: SelectQuery): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.GE, QuerySub(query))
    }

    infix fun lt(query: Query): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.LT, query)
    }

    infix fun <T> lt(query: T): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.LT, const(query))
    }

    infix fun lt(query: SelectQuery): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.LT, QuerySub(query))
    }

    infix fun le(query: Query): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.LE, query)
    }

    infix fun <T> le(query: T): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.LE, const(query))
    }

    infix fun le(query: SelectQuery): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.LE, QuerySub(query))
    }

    fun isNull(): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.IS, null)
    }

    fun isNotNull(): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.IS_NOT, null)
    }

    infix fun like(query: String): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.LIKE, const(query))
    }

    infix fun notLike(query: String): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.NOT_LIKE, const(query))
    }

    private fun <T> inList(query: Query, list: List<T>, isNot: Boolean = false): QueryInList<T> {
        return QueryInList(query, list, isNot)
    }

    private fun inList(query: Query, subQuery: SelectQuery, isNot: Boolean = false): QueryInSubQuery {
        return QueryInSubQuery(query, subQuery, isNot)
    }

    infix fun <T> inList(query: List<T>): QueryInList<T> {
        return inList(this, query)
    }

    infix fun inList(query: SelectQuery): QueryInSubQuery {
        return inList(this, query)
    }

    infix fun <T> notInList(query: List<T>): QueryInList<T> {
        return inList(this, query, true)
    }

    infix fun notInList(query: SelectQuery): QueryInSubQuery {
        return inList(this, query, true)
    }

    infix fun and(query: Query): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.AND, query)
    }

    infix fun or(query: Query): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.OR, query)
    }

    infix fun xor(query: Query): QueryBinary {
        return QueryBinary(this, SqlBinaryOperator.XOR, query)
    }

    fun <T> between(start: T, end: T): QueryBetween<T> {
        return QueryBetween(this, start, end)
    }

    fun <T> notBetween(start: T, end: T): QueryBetween<T> {
        return QueryBetween(this, start, end, true)
    }

    infix fun json(value: String): QueryJson {
        return QueryJson(this, SqlBinaryOperator.SUB_GT, value)
    }

    infix fun json(value: Int): QueryJson {
        return QueryJson(this, SqlBinaryOperator.SUB_GT, value)
    }

    infix fun jsonText(value: String): QueryJson {
        return QueryJson(this, SqlBinaryOperator.SUB_GT_GT, value)
    }

    infix fun jsonText(value: Int): QueryJson {
        return QueryJson(this, SqlBinaryOperator.SUB_GT_GT, value)
    }

    infix fun alias(alias: String): Query {
        this.alias = alias
        return this
    }

    fun asc(): OrderBy {
        return OrderBy(this, SqlOrderByOption.ASC)
    }

    fun desc(): OrderBy {
        return OrderBy(this, SqlOrderByOption.DESC)
    }
}

data class QueryExpr(val expr: SqlExpr, override var alias: String? = null) : Query()

data class QueryColumn(val column: String, override var alias: String? = null) : Query()

data class QueryExprFunction(val name: String, val args: List<Query>, override var alias: String? = null) : Query()

data class QueryAggFunction(
    val name: String,
    val args: List<Query>,
    val distinct: Boolean = false,
    val attributes: Map<String, Query>? = null,
    val orderBy: List<OrderBy> = listOf(),
    override var alias: String? = null
) : Query() {
    fun over(): QueryOver {
        return QueryOver(this, listOf(), listOf())
    }
}

data class QueryConst<T>(val value: T, override var alias: String? = null) : Query()

data class QueryBinary(
    val left: Query,
    val operator: SqlBinaryOperator,
    val right: Query?,
    override var alias: String? = null
) :
    Query() {
    infix fun <T> then(then: T): CaseBranch<T> {
        return CaseBranch(this, then)
    }
}

data class QuerySub(val selectQuery: SelectQuery, override var alias: String? = null) : Query()

data class QueryCase<T>(
    val conditions: List<CaseBranch<T>>,
    val default: T? = null,
    override var alias: String? = null
) : Query() {
    infix fun elseIs(value: T?): QueryCase<T> {
        return if (value != null) {
            QueryCase(this.conditions, value)
        } else {
            this
        }
    }
}

data class QueryTableColumn(
    val table: String,
    val column: String,
    var incr: Boolean = false,
    var primaryKey: Boolean = false,
    override var alias: String? = null
) : Query() {
    fun incr(): QueryTableColumn {
        this.incr = true
        return this
    }

    fun primaryKey(): QueryTableColumn {
        this.primaryKey = true
        return this
    }
}

data class QueryJson(
    val query: Query,
    val operator: SqlBinaryOperator,
    val value: Any,
    override var alias: String? = null
) : Query()

data class QueryCast(val query: Query, val type: String, override var alias: String? = null) : Query()

data class QueryInList<T>(
    val query: Query,
    val list: List<T>,
    val isNot: Boolean = false,
    override var alias: String? = null
) : Query()

data class QueryInSubQuery(
    val query: Query,
    val subQuery: SelectQuery,
    val isNot: Boolean = false,
    override var alias: String? = null
) : Query()

data class QueryBetween<T>(
    val query: Query,
    val start: T,
    val end: T,
    val isNot: Boolean = false,
    override var alias: String? = null
) : Query()

data class QueryAllColumn(val owner: String? = null, override var alias: String? = null) : Query()

data class QueryOver(
    val function: QueryAggFunction,
    val partitionBy: List<Query> = listOf(),
    val orderBy: List<OrderBy> = listOf(),
    override var alias: String? = null
) : Query() {
    fun partitionBy(vararg query: Query): QueryOver {
        return QueryOver(this.function, this.partitionBy + query, this.orderBy)
    }

    infix fun partitionBy(query: Query): QueryOver {
        return QueryOver(this.function, this.partitionBy + query, this.orderBy)
    }

    infix fun partitionBy(query: List<Query>): QueryOver {
        return QueryOver(this.function, this.partitionBy + query, this.orderBy)
    }

    fun orderByAsc(vararg order: Query): QueryOver {
        val orderBy = order.map { OrderBy(it, SqlOrderByOption.ASC) }
        return QueryOver(this.function, this.partitionBy, this.orderBy + orderBy)
    }

    fun orderByDesc(vararg order: Query): QueryOver {
        val orderBy = order.map { OrderBy(it, SqlOrderByOption.DESC) }
        return QueryOver(this.function, this.partitionBy, this.orderBy + orderBy)
    }

    infix fun orderByAsc(order: Query): QueryOver {
        return QueryOver(this.function, this.partitionBy, this.orderBy + OrderBy(order, SqlOrderByOption.ASC))
    }

    infix fun orderByDesc(order: Query): QueryOver {
        return QueryOver(this.function, this.partitionBy, this.orderBy + OrderBy(order, SqlOrderByOption.DESC))
    }

    infix fun orderByAsc(order: List<Query>): QueryOver {
        val orderBy = order.map { OrderBy(it, SqlOrderByOption.ASC) }
        return QueryOver(this.function, this.partitionBy, this.orderBy + orderBy)
    }

    infix fun orderByDesc(order: List<Query>): QueryOver {
        val orderBy = order.map { OrderBy(it, SqlOrderByOption.DESC) }
        return QueryOver(this.function, this.partitionBy, this.orderBy + orderBy)
    }

    fun orderBy(vararg order: OrderBy): QueryOver {
        val orderByList = order.toList()
        return QueryOver(this.function, this.partitionBy, this.orderBy + orderByList)
    }

    infix fun orderBy(order: OrderBy): QueryOver {
        return QueryOver(this.function, this.partitionBy, this.orderBy + order)
    }

    infix fun orderBy(order: List<OrderBy>): QueryOver {
        return QueryOver(this.function, this.partitionBy, this.orderBy + order)
    }
}

data class QuerySubQueryPredicate(
    val query: SelectQuery,
    val predicate: SqlSubQueryPredicate,
    override var alias: String? = null
) : Query()

data class CaseBranch<T>(val query: Query, val then: T)

data class OrderBy(val query: Query, val order: SqlOrderByOption)